
package ru.samgtu.wst.util.swing;

import javax.swing.tree.TreePath;
import javax.swing.tree.TreeModel;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.EventListenerList;



public abstract class AbstractTreeModel implements TreeModel {

	protected EventListenerList listeners;

	protected AbstractTreeModel() {
		listeners = new EventListenerList();
	}

	public int getIndexOfChild(Object parent, Object child) {
		for (int count = getChildCount(parent), i = 0; i < count; i++) {
			if (getChild(parent, i).equals(child)) {
				return i;
			}
		}

		return -1;
	}

	/** Call when there is a new root, which may be null, i.e. not existent. */
	protected void fireNewRoot() {
		Object[] pairs = listeners.getListenerList();

		Object root = getRoot();

		/*
		 * Undocumented. I think it is the only reasonable/possible solution to
		 * use use null as path if there is no root. TreeModels without root
		 * aren't important anyway, since JTree doesn't support them (yet).
		 */
		TreePath path = (root != null) ? new TreePath(root) : null;

		TreeModelEvent e = null;

		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == TreeModelListener.class) {
				if (e == null) {
					e = new TreeModelEvent(this, path, null, null);
				}

				((TreeModelListener) pairs[i + 1]).treeStructureChanged(e);
			}
		}
	}

	/**
	 * Call when everything but the root has changed. Only may be called when
	 * the root is not null. Otherwise there isn't a structure to have changed.
	 */
	protected void fireStructureChanged() {
		fireTreeStructureChanged(new TreePath(getRoot()));
	}

	/** Call when a node has changed its leaf state. */
	protected void firePathLeafStateChanged(TreePath path) {
		fireTreeStructureChanged(path);
	}

	/** Call when the tree structure below the path has completely changed. */
	protected void fireTreeStructureChanged(TreePath parentPath) {
		Object[] pairs = listeners.getListenerList();

		TreeModelEvent e = null;

		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == TreeModelListener.class) {
				if (e == null) {
					e = new TreeModelEvent(this, parentPath, null, null);
				}

				((TreeModelListener) pairs[i + 1]).treeStructureChanged(e);
			}
		}
	}

	/**
	 * Call when the path itself has changed, but no structure changes have
	 * occurred.
	 */
	protected void firePathChanged(TreePath path) {
		Object node = path.getLastPathComponent();
		TreePath parentPath = path.getParentPath();

		if (parentPath == null) {
			fireChildrenChanged(path, null, null);
		} else {
			Object parent = parentPath.getLastPathComponent();

			fireChildChanged(parentPath, getIndexOfChild(parent, node), node);
		}
	}

	protected void fireChildAdded(TreePath parentPath, int index, Object child) {
		fireChildrenAdded(parentPath, new int[] { index },
				new Object[] { child });
	}

	protected void fireChildChanged(TreePath parentPath, int index, Object child) {
		fireChildrenChanged(parentPath, new int[] { index },
				new Object[] { child });
	}

	protected void fireChildRemoved(TreePath parentPath, int index, Object child) {
		fireChildrenRemoved(parentPath, new int[] { index },
				new Object[] { child });
	}

	protected void fireChildrenAdded(TreePath parentPath, int[] indices,
			Object[] children) {
		Object[] pairs = listeners.getListenerList();

		TreeModelEvent e = null;

		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == TreeModelListener.class) {
				if (e == null) {
					e = new TreeModelEvent(this, parentPath, indices, children);
				}

				((TreeModelListener) pairs[i + 1]).treeNodesInserted(e);
			}
		}
	}

	protected void fireChildrenChanged(TreePath parentPath, int[] indices,
			Object[] children) {
		Object[] pairs = listeners.getListenerList();

		TreeModelEvent e = null;

		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == TreeModelListener.class) {
				if (e == null) {
					e = new TreeModelEvent(this, parentPath, indices, children);
				}

				((TreeModelListener) pairs[i + 1]).treeNodesChanged(e);
			}
		}
	}

	protected void fireChildrenRemoved(TreePath parentPath, int[] indices,
			Object[] children) {
		Object[] pairs = listeners.getListenerList();

		TreeModelEvent e = null;

		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == TreeModelListener.class) {
				if (e == null) {
					e = new TreeModelEvent(this, parentPath, indices, children);
				}
				((TreeModelListener) pairs[i + 1]).treeNodesRemoved(e);
			}
		}
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		AbstractTreeModel clone = (AbstractTreeModel) super.clone();

		clone.listeners = new EventListenerList();

		return clone;
	}

	public void addTreeModelListener(TreeModelListener l) {
		listeners.add(TreeModelListener.class, l);
	}

	public void removeTreeModelListener(TreeModelListener l) {
		listeners.remove(TreeModelListener.class, l);
	}
}
